/*
 * DatabaseListResource.java
 *
 * Created on November 23, 2007, 4:03 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.app.admin;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.atomojo.app.App;
import org.atomojo.app.Storage;
import org.atomojo.app.StorageFactory;
import org.atomojo.app.auth.User;
import org.atomojo.app.client.XMLRepresentationParser;
import org.atomojo.app.db.DB;
import org.atomojo.app.db.DBInfo;
import org.atomojo.app.db.RemoteApp;
import org.atomojo.app.db.SyncProcess;
import org.atomojo.app.db.SyncTarget;
import org.atomojo.app.sync.PullSynchronizer;
import org.atomojo.app.sync.SyncException;
import org.infoset.xml.Document;
import org.infoset.xml.Element;
import org.infoset.xml.XMLException;
import org.infoset.xml.util.DocumentDestination;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.ServerResource;

/**
 *
 * @author alex
 */
public class AllDatabaseRestoreResource extends ServerResource
{
   
   XMLRepresentationParser parser = new XMLRepresentationParser();
   /** Creates a new instance of SyncResource */
   public AllDatabaseRestoreResource() {
      setNegotiated(false);
   }
   
   public Representation get()
   {
      return null;
   }
   
   public Representation post(Representation entity)
   {
      if (!XMLRepresentationParser.isXML(entity.getMediaType())) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("Non-XML media type for entity body: "+entity.getMediaType().getName());
      }
      
      Map<String,DBInfo> dbList = (Map<String,DBInfo>)getContext().getAttributes().get(DatabaseListResource.DB_LIST);
      Map<String,DBInfo> autodbList = (Map<String,DBInfo>)getContext().getAttributes().get(DatabaseListResource.AUTO_DB_LIST);
      StorageFactory storageFactory = (StorageFactory)getContext().getAttributes().get(DatabaseListResource.STORAGE_FACTORY);

      try {
         DocumentDestination dest = new DocumentDestination();
         
         parser.parse(entity,AdminApplication.createAdminDocumentDestination(dest,AdminXML.NM_RESTORE));
         Document doc = dest.getDocument();
         
         Element top = doc.getDocumentElement();
         String location = top.getAttributeValue("location");
         if (location==null) {
            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return new StringRepresentation("The 'location' attribute is missing.");
         }
         location = location.trim();
         if (location.length()==0) {
            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return new StringRepresentation("The 'location' attribute is empty.");
         }
         File dir = new File(location);
         if (!dir.exists()) {
            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return new StringRepresentation("The "+dir.getAbsolutePath()+" doesn't exist.");
         }
         if (!dir.canRead()) {
            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return new StringRepresentation("Cannot write to "+dir.getAbsolutePath());
         }

         Set<String> dbNames = new TreeSet<String>();
         Iterator<Element> names = top.getElementsByName(AdminXML.NM_NAME);
         while (names.hasNext()) {
            dbNames.add(names.next().getText());
         }
         
         List<Map<String,DBInfo>> lists = new ArrayList<Map<String,DBInfo>>();
         lists.add(dbList);
         lists.add(autodbList);
         
         User user = (User)getRequest().getAttributes().get(App.USER_ATTR);
         
         boolean ok = true;
         List<String> failures = new ArrayList<String>();
         for (Map<String,DBInfo> map : lists) {
            for (DBInfo dbinfo : map.values()) {
               DB db = dbinfo.getDB();
               if (dbNames.size()>0 && !dbNames.contains(db.getName())) {
                  continue;
               }
               File dbDir = new File(dir,db.getName());
               boolean needsCleanup = false;
               try {
                  Storage storage = storageFactory.getStorage(db);
                  File zipFile = new File(dir,db.getName()+".zip");
                  if (!dbDir.exists() && zipFile.exists()) {
                     // This is what we want but it doesn't work
                     //introspectionURI = new URI("jar:"+zipFile.toURI()+"!/_introspection_.xml");
                     getLogger().info("Extracting backup for restore...");
                     needsCleanup = true;
                     extract(zipFile,zipFile.getParentFile());
                  }
                  
                  File introspect = new File(dbDir,"_introspection_.xml");
                  if (!introspect.exists()) {
                     getLogger().info("No database backup for database "+db.getName());
                     continue;
                  }
                  URI introspectionURI = introspect.toURI();

                  RemoteApp app = new RemoteApp(db,"backup");
                  app.setIntrospection(introspectionURI);
                  app.setRoot(dbDir.toURI());

                  SyncTarget target = new SyncTarget(db,"");
                  SyncProcess proc = new SyncProcess(db,"restore",true,target,app,null);

                  PullSynchronizer restore = new PullSynchronizer(getLogger(),getApplication().getMetadataService(),user,db,storage,proc);
                  restore.setAdditive(false);
                  try {
                     restore.sync();
                     if (restore.getErrorCount()>0) {
                        ok = false;
                        getLogger().severe("Restore failed on "+db.getName());
                        failures.add(db.getName());
                     }
                  } catch (SyncException ex) {
                     getLogger().log(Level.SEVERE,"Restore failed on "+db.getName()+" due to exception.",ex);
                     ok = false;
                     failures.add(db.getName());
                  }

               } catch (Exception ex) {
                  getLogger().log(Level.SEVERE,"Cannot restore database "+db.getName()+" due to exception.",ex);
                  ok = false;
                  failures.add(db.getName());
               }
               if (needsCleanup) {
                  delete(dbDir);
               }
            }
         }
         
         if (ok) {
            getResponse().setStatus(Status.SUCCESS_CREATED);
            return null;
         } else {
            StringBuilder message = new StringBuilder();
            message.append("Failure on databases:");
            for (String name : failures) {
               message.append(" ");
               message.append(name);
            }
            getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
            return new StringRepresentation(message.toString());
         }
         
      } catch (IOException ex) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("I/O exception: "+ex.getMessage());
      } catch (XMLException ex) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("XML exception: "+ex.getMessage());
      }
   }
   
   public void extract(File zipFile, File targetDir) 
      throws IOException
   {
      byte[] buffer = new byte[16384];
      ZipInputStream zipInput = new ZipInputStream(new FileInputStream(zipFile));
      ZipEntry zipEntry = zipInput.getNextEntry();
      while (zipEntry != null) {
          //for each entry to be extracted
         if (zipEntry.isDirectory()) {
            File dir = new File(targetDir,zipEntry.getName());
            getLogger().info("Creating: "+dir);
            if (!dir.mkdirs()) {
               throw new IOException("Cannot make directory path: "+dir);
            }
         } else {
            File targetFile = new File(targetDir,zipEntry.getName());
            if (!targetFile.getParentFile().exists()) {
               getLogger().info("Creating: "+targetFile.getParentFile());
               if (!targetFile.getParentFile().mkdirs()) {
                  throw new IOException("Cannot make directory path: "+targetFile.getParentFile());
               }
            }
            getLogger().info("Extracting: "+targetFile);
            FileOutputStream output = new FileOutputStream(targetFile);
            int len = 0;
            while ((len=zipInput.read(buffer,0,buffer.length))>-1) {
               output.write(buffer,0,len);
            }
            output.flush();
            output.close();
         }
         zipInput.closeEntry();
         zipEntry = zipInput.getNextEntry();
      }

      zipInput.close();
   }

   public void delete(File file) {
      final List<File> queue = new ArrayList<File>();
      queue.add(file);
      boolean ok = true;
      int mark = -1;
      while (ok && queue.size()>0) {
         File target = queue.remove(queue.size()-1);
         if (target.isDirectory()) {
            if (mark==queue.size()) {
               ok = target.delete();
               mark = -1;
            } else {
               mark = queue.size();
               queue.add(target);
               target.listFiles(new FileFilter() {
                  public boolean accept(File f)
                  {
                     queue.add(f);
                     return false;
                  }
               });
            }
         } else {
            ok = target.delete();
         }
      }

   }
}
